/**
 * Base class for all Chord nodes
 **/
package core1;

import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import processing.core.PApplet;

public class ChordNode extends ChordObject {

	// instance variables
	protected Globals globals = Globals.getInstance();
	protected Map<Long,Nodeable> fingerTable; 
	protected Nodeable next;
	protected Nodeable previous;
	protected Nodeable user;
	
	protected long nextKey;
	protected long previousKey;

	protected List<ChordElement> elements;
	private float size = globals.nodeSize;

	//Constructor
	public ChordNode(PApplet parent) {
		super(parent);
		this. parent = parent;
		init();
	}
	
	public ChordNode(PApplet parent,long key) {
		super(parent,key);
		this. parent = parent;
		init();
	}
	
	private void init() {
		
		fingerTable = new LinkedHashMap<Long, Nodeable>();
		elements = new LinkedList<ChordElement>();
		
		//Create empty finger table skeleton

		long offset = getMaxKey();
		long myKey = getKey();
		long maxKey = getMaxKey();
		long fingerKey = 1;
		while (offset >= 1)
		{
			offset = offset / 2;
			fingerKey = (myKey + offset) % maxKey;
			fingerTable.put(fingerKey , null);
		}
	}
	
	@Override
	public void update()
	{
		if (!globals.logicView)
			setPhysicalView();
		super.update();
	}
	
	// Draw at each frame
	@Override
	public void draw(float scale ){
		update();
		float x= getXpos();
		float y= getYpos();

		// draw finger table

		if (globals.drawFullFingerTables) {
			parent.stroke(globals.FingerColor);
			parent.strokeWeight(2/scale);
			
			float curvature = (1-globals.curvature);
			
			//If the chord lines should be drawn
			if (globals.networkMode == Globals.CHORD_MODE) {
				
				Nodeable justDrawn=null;
				for (Entry<Long,Nodeable> finger: fingerTable.entrySet())
				{
					Nodeable other = finger.getValue();
					
					if (other != null && other != this && other != justDrawn ){
						float thatx = other.getXpos();
						float thaty = other.getYpos();
						//parent.line(x,y,thatx,thaty);
						parent.bezier(x,y,x*curvature,y*curvature,thatx*curvature,thaty*curvature,thatx,thaty);
						
						justDrawn= other;
					}
				}
				
				// The bezier curve to the predecessor node
				if (globals.drawPreds && previous != this) {
					parent.stroke(globals.PredColor);
					parent.strokeWeight(2/scale);
					float predCurvature = (1+globals.predCurvature);
					float thatx = previous.getXpos();
					float thaty = previous.getYpos();
					parent.bezier(x,y,x*predCurvature,y*predCurvature,thatx*predCurvature,thaty*predCurvature,thatx,thaty);
				}
				
			//If the mesh lines should be drawn
			} else if (globals.networkMode == Globals.MESH_MODE) {
				
				for( Nodeable other : ((Main) parent).nodes) {
							
					if (other != null && other != this) {
						float thatx = other.getXpos();
						float thaty = other.getYpos();
						//parent.line(x,y,thatx,thaty);
						parent.bezier(x,y,x*curvature,y*curvature,thatx*curvature,thaty*curvature,thatx,thaty);
					}
				}
			} else if (globals.networkMode == Globals.SIMPLE_MODE) {
				
				if (next != null && next != this ){
					float thatx = next.getXpos();
					float thaty = next.getYpos();
					//parent.line(x,y,thatx,thaty);
					parent.bezier(x,y,x*curvature,y*curvature,thatx*curvature,thaty*curvature,thatx,thaty);
				}
				
				// The bezier curve to the predecessor node
				if (globals.drawPreds && previous != this) {
					parent.stroke(globals.PredColor);
					parent.strokeWeight(2/scale);
					float predCurvature = (1+globals.predCurvature);
					float thatx = previous.getXpos();
					float thaty = previous.getYpos();
					parent.bezier(x,y,x*predCurvature,y*predCurvature,thatx*predCurvature,thaty*predCurvature,thatx,thaty);
				}
				
			} 
		}
		
		
		
		// The filled background circle
		parent.noStroke();
		parent.fill(globals.NodeColor);
		parent.ellipse(x, y, size/scale, size/scale);
		
		// The sharp circle edge around the filled circle
		parent.stroke(globals.NodeFrameColor);
		parent.strokeWeight(2/scale);
		parent.ellipse(x,y, size/scale, size/scale);
		
		if (globals.drawText) {
			drawText(scale);
		}
	}
	
	// second draw function that is called after the first. 
	// this gets drawn on top of draw();
	public void draw2(float scale){
		
		float x= getXpos();
		float y= getYpos();
	
		
		// The filled background circle
		parent.noStroke();
		parent.fill(globals.NodeColor);
		parent.ellipse(x, y, size/scale, size/scale);
		
		// The sharp circle edge around the filled circle
		parent.stroke(globals.NodeFrameColor);
		parent.strokeWeight(2/scale);
		parent.ellipse(x,y, size/scale, size/scale);
		
		if (globals.drawText) {
			drawText(scale);
		
		
	}}

	public void drawText(float scale) {
		
		float textxsize = 100;
		float textysize = 30;
		
		float xfactor = 1 + (textxsize/scale) / globals.radius;
		float yfactor = 1 + (textysize/scale) / globals.radius;
		
		float x= getXpos()+10;
		float y= getYpos()+10;
		
		parent.textSize(8/(scale*0.8f));
		parent.fill( globals.TextColor);
		parent.text("key: " + getKey() + "\n" +
				"elements: " + elements.size() + "\n" +
				
				"", x, y);
	}
	
	public void onMouseOver(float scale) {

		float x= getXpos();
		float y= getYpos();

		parent.stroke(globals.SelectedFingerColor);
		parent.strokeWeight(3/scale);

		float curvature = (1-globals.curvature);
		
		if (parent.keyPressed && parent.keyCode == PApplet.SHIFT) // require shift to be pressed to highlight fingers
		{

		if (globals.networkMode == Globals.CHORD_MODE) {
			
			for (Entry<Long,Nodeable> finger:fingerTable.entrySet()) {
				
				Nodeable other = finger.getValue();
				
				// Don't do the alreadyDrawn check for mouseOver, becuase then
				// the lines aren't shown as bold red (the mouseover color) in cases where they've
				// already been drawn as blue. 
				
				if(other != null && other != this ) {
					
					float thatx = other.getXpos();
					float thaty = other.getYpos();
					//parent.line(x,y,thatx,thaty);
					parent.bezier(x,y,x*curvature,y*curvature,thatx*curvature,thaty*curvature,thatx,thaty);
					
				}
				
			}
			
			// The bezier curve to the predecessor node
			if (previous != this && globals.drawPreds) {
				parent.stroke(globals.SelectedPredColor);
				parent.strokeWeight(3/scale);
				float predCurvature = (1+globals.predCurvature);
				float thatx = previous.getXpos();
				float thaty = previous.getYpos();
				parent.bezier(x,y,x*predCurvature,y*predCurvature,thatx*predCurvature,thaty*predCurvature,thatx,thaty);
			}

		} else if (globals.networkMode == Globals.MESH_MODE) {
			for( Nodeable other : ((Main)parent).nodes ){
				
				if (other != null && other != this) {
					
					float thatx = other.getXpos();
					float thaty = other.getYpos();
					//parent.line(x,y,thatx,thaty);
					parent.bezier(x,y,x*curvature,y*curvature,thatx*curvature,thaty*curvature,thatx,thaty);
					
				}
			}
		} else if (globals.networkMode == Globals.SIMPLE_MODE) {
			
			// Don't do the alreadyDrawn check for mouseOver, becuase then
			// the lines aren't shown as bold red (the mouseover color) in cases where they've
			// already been drawn as blue. 
			
			if(next != null && next != this ) {
				
				float thatx = next.getXpos();
				float thaty = next.getYpos();
				//parent.line(x,y,thatx,thaty);
				parent.bezier(x,y,x*curvature,y*curvature,thatx*curvature,thaty*curvature,thatx,thaty);
				
			}
				
			// The bezier curve to the predecessor node
			if (previous != this && globals.drawPreds) {
				parent.stroke(globals.SelectedPredColor);
				parent.strokeWeight(3/scale);
				float predCurvature = (1+globals.predCurvature);
				float thatx = previous.getXpos();
				float thaty = previous.getYpos();
				parent.bezier(x,y,x*predCurvature,y*predCurvature,thatx*predCurvature,thaty*predCurvature,thatx,thaty);
			}
			
		}
		
		} // end giant SHIFT if clause
		
		
		//draw finger table TARGETS  (CTRL)
		if (parent.keyPressed && parent.keyCode == PApplet.CONTROL && globals.networkMode == Globals.CHORD_MODE)
			
		{
			parent.pushStyle();
			
			parent.stroke(globals.setOpacity(globals.SelectedFingerColor,100));
			
			
			double r = Globals.radius;
                for (long finger:fingerTable.keySet()) {
                	
                	
                	
     
            		float thatx = (float) r* (float) Math.cos(Math.toRadians(getRingPosition(finger)));
            		float thaty = (float) r* (float) Math.sin(Math.toRadians(getRingPosition(finger)));
            		curvature= (1-globals.curvature*(distance(x, y,thatx, thaty)/globals.radius)*2);
            		parent.bezier(x,y,x*curvature,y*curvature,thatx*curvature,thaty*curvature,thatx,thaty);
                }
				
                
    			parent.popStyle();
				

					
				}
			
			
		
		
		// The filled background circle
		parent.noStroke();
		parent.fill(globals.SelectedNodeColor);
		parent.ellipse(x, y, size/scale, size/scale );
		
		// The sharp circle edge around the filled circle
		parent.stroke(globals.SelectedNodeFrameColor);
		parent.strokeWeight(1/scale);
		parent.ellipse(x,y, size/scale, size/scale);
		
		drawText(scale);
	}
	public float distance (float x1,float y1, float x2, float y2)
	{
		return (float) Math.sqrt(Math.pow(x1-x2,2)+Math.pow(y1- y2,2));
		
		
		
	}
	
	
	public void setNext(Nodeable next) {
		this.next = next;
		this.nextKey = next.getKey();
	}

	public void setPrevious(Nodeable previous) {
		this.previous = previous;
		this.previousKey = previous.getKey ();
	}

	public Nodeable getNext() {
		return next;
	}

	public Nodeable getPrevious() {
		return previous;
	}

	public int getElementCount() {
		return elements.size();
	}
	
	public void setPhysicalView()
	{
		float offset= 0;
		float step= 8/globals.scale;

		for (ChordElement e: elements){
			offset += step ; 
			e.setTarget(getXpos(), getYpos()+offset);

		}
	}
}
